package org.example.novelcrawler.service;

import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.StringUtils;
import org.example.novelcrawler.model.Chapter;
import org.example.novelcrawler.model.CrawlConfig;
import org.example.novelcrawler.util.FileUtil;
import org.example.novelcrawler.util.JSUtil;
import org.example.novelcrawler.util.XPathQueryHelper;
import org.jsoup.Connection;
import org.jsoup.HttpStatusException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.seimicrawler.xpath.JXNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class NovelCrawlerServiceImpl {

    @Autowired
    private FileUtil fileUtil;

    @Value("${crawler.output-dir}")
    private String outputDir;

    @Value("${crawler.timeout}")
    private int timeout;

    public String crawlNovel(CrawlConfig config, String addr) throws IOException, InterruptedException {
         Map<String, String> chapterMap=parseTableOfContents(addr,config); // URL -> Title
        if(null==chapterMap){
            System.out.println("目录请求异常");
            return "Fail";
        }
        System.out.println("发现 " + chapterMap.size() + " 个章节，开始爬取...");
        // 2. 按顺序爬取每个章节
        int chapterNum = 1;
        StringBuilder  fullBook=new StringBuilder();
        for (Map.Entry<String, String> entry : chapterMap.entrySet()) {
            String chapterUrl = entry.getKey();
            String chapterTitle = entry.getValue();

            System.out.printf("正在爬取 (%d/%d): %s%n",
                    chapterNum, chapterMap.size(), chapterTitle);

            // 爬取单个章节
            String chapterContent = crawlChapter(chapterUrl,config,addr);

            // 组合内容
            fullBook.append("第").append(chapterNum).append("章 ")
                    .append(chapterTitle).append("\n")
                    .append(chapterContent).append("\n\n");

            chapterNum++;

            // 延迟控制
            if (chapterNum <= chapterMap.size()) {
                TimeUnit.MILLISECONDS.sleep(config.getChapterInterval());
            }
        }

        // 开发环境 resources 目录：可永久保存
        String resourcesPath = System.getProperty("user.dir") + "/src/main/resources/book";
        // 保存完整小说
        String[] strs= addr.split("/");
        String bookName="";
        for (int i = strs.length-1; i >=0 ; i--) {
            if(!StringUtils.isEmpty(strs[i])){
                bookName=strs[i];
                break;
            }
        }
        fileUtil.saveAllToTxt(resourcesPath,bookName, fullBook.toString());
        System.out.println("爬取完成，共爬取 " + (chapterNum - 1) + " 章");
        return "爬取完成，共爬取 " + (chapterNum - 1) + " 章";
    }

    /**
     * 解析目录页，填充 chapterMap
     */
    private Map<String,String> parseTableOfContents(String tocUrl,CrawlConfig config) throws IOException {
        Document doc = fetchDocument(tocUrl,null,config);
        if(!doc.select("error").isEmpty()){
            return null;
        }
        Elements elements= doc.select(config.getNextPageSelector());

        if (elements.isEmpty()) {
            throw new IOException("未找到章节链接，选择器: " + config.getNextPageSelector());
        }
        LinkedHashMap<String,String>  chapterMap=new LinkedHashMap<>();
        for (Element link : elements) {
            String url =config.getStartUrl()+link.select("a").attr("href");// 转换为绝对URL
            String title=link.select("a").text();
            if(title.contains("章")){
                title=title.substring(title.indexOf("章")+1);
            }

            // 过滤无效条目
            if (url.isEmpty() || title.contains("下一页") || title.contains("上一页")) {
                continue;
            }

            chapterMap.put(url, title);
        }
        return chapterMap;
    }

    private Document fetchDocument(String url,String ogrin,CrawlConfig config) throws IOException {
        String Host=url.startsWith("https")?url.replace("https://",""):url.replace("http://","");
        Host=Host.split("/")[0];
        Map headers= config.getHeaders();
        Connection  connection= Jsoup.connect(url)
                // 1. 必须设置的请求头
                .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0")
                .header("Origin", url) // 来源
                .header("Referer", ogrin) // 来源页
                // 3. 技术配置
                .timeout(20000)  // 20秒超时
                .maxBodySize(0)  // 不限制响应大小
                .followRedirects(true) // 跟随重定向
                .referrer(url)
                .timeout(10000)
                .referrer(url);
        if(!StringUtils.isEmpty(config.getMethodType())){
            switch (config.getMethodType()){
                case "GET": connection.method(Connection.Method.GET); break;
                case "POST": connection.method(Connection.Method.POST); break;
                case "PUT": connection.method(Connection.Method.PUT); break;
                default: connection.method(Connection.Method.GET); break;
            }
        }else {
            connection.method(Connection.Method.GET);
        }
                if(!headers.isEmpty()){
                    headers.forEach((key,value) -> {
                        connection.header(key.toString(),value.toString());
                    });
                }
        try {
            // 发送 GET 请求并获取响应
            Connection.Response response = connection.execute();
            if(null==headers.get("cookie")||"".equals(headers.get("cookie").toString())){
                // 获取响应头中的所有 Cookie
                Map<String, String> cookies = response.cookies();
                if(!cookies.isEmpty()){
                    // 打印所有 Cookie
                    for (Map.Entry<String, String> cookie : cookies.entrySet()) {
                        System.out.println("Cookie: " + cookie.getKey() + " = " + cookie.getValue());
                    }
                    headers.put("cookie",cookies.values().toArray()[0].toString());
                }
            }
            return Jsoup.parse(response.readBody());
        } catch (HttpStatusException e) {
            // 处理HTTP错误（404、500等）
            System.out.println(e.getMessage());
            return Jsoup.parse("<error>"+ogrin+"->"+url+"请求失败：异常："+e.getMessage()+"</error>") ;
        } catch (SocketTimeoutException e) {
           return Jsoup.parse("<error>"+ogrin+"->"+url+"请求失败:超时"+"</error>");
        } catch (IOException e) {
            return Jsoup.parse("<error>"+ogrin+"->"+url+"请求失败:超时"+"</error>");
        }
    }

    //请求重试-上限5次
    private Document reTryHttp(String chapterUrl,String mulu,CrawlConfig config){
        int i=0;
        Document document=null;
        while (i<5){
            System.out.println(chapterUrl+"请求异常，请求重试"+i+"次");
            try {
                document= fetchDocument(chapterUrl,mulu,config);
            } catch (IOException e) {
                System.out.println(chapterUrl+"   第"+i+"次请求异常，请求重试"+(i+1)+"次");
            }
            i++;
        }
        if(null==document){
            System.out.println(chapterUrl+"五次请求失败");
        }
        return document;
    }

    /**
     * 爬取单个章节（含分页处理）
     */
    private String crawlChapter(String chapterUrl,CrawlConfig config,String mulu) throws IOException {
        Document doc = fetchDocument(chapterUrl,mulu,config);
        if(doc.html().contains("请求失败")){
            System.out.println(doc.html());
            doc= reTryHttp(chapterUrl,mulu,config);
            if(null==doc){
                return chapterUrl;
            }
        }
        // 提取主内容
        Element contentElements=doc.selectFirst(config.getContentSelector());
        String html=contentElements.html();
        html=html.replaceAll("<.*?>", "");
        StringBuilder content=new StringBuilder(html);
        // 处理分页（同一章节内的多页）
        String nextPageUrl = extractNextPageUrl(doc,config);
        while (!StringUtils.isEmpty(nextPageUrl)) {
            System.out.println("  发现分页: " + nextPageUrl);

            Document nextPageDoc = fetchDocument(nextPageUrl.contains(config.getStartUrl())?nextPageUrl:config.getStartUrl()+nextPageUrl,chapterUrl,config);
            if(nextPageDoc.html().contains("请求失败")){
                nextPageDoc= reTryHttp(nextPageUrl,chapterUrl,config);
                if(null==nextPageDoc){
                    return nextPageUrl;
                }
            }
            contentElements = nextPageDoc.selectFirst(config.getContentSelector());
            if (null!=contentElements) {
                content.append("\n").append(contentElements.html().replaceAll("<.*?>", ""));
            }
            if((contentElements.text().contains(config.getNextLoopTitle()))){
                nextPageUrl = extractNextPageUrl(nextPageDoc,config);
            }else{
                nextPageUrl=null;
            }
            // 分页间延迟
            try {
                TimeUnit.MILLISECONDS.sleep(config.getChapterInterval());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }

        return content.toString();
    }

    private String extractNextPageUrl(Document doc,CrawlConfig config) {
        if (config.getNextLoop() == null || config.getNextLoop().isEmpty()) {
            return null;
        }

        Element nextPageElement = doc.selectFirst(config.getNextLoop());
        if (nextPageElement != null&&nextPageElement.text().contains(config.getNextLoopTitle()) && nextPageElement.hasAttr("href")) {
            return nextPageElement.attr("href");
        }
        return null;
    }

    private Chapter crawlChapter(String url, int chapterIndex, CrawlConfig config) {
        try {
            Document doc = Jsoup.connect(url.startsWith("http")?url:"http://"+url)
                    .timeout(timeout)
                    .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
                    .get();

            // 提取标题
            String title = doc.select(config.getTitleSelector()).first().text();

            // 提取内容
            Element contentElement = doc.select(config.getContentSelector()).first();
            if (contentElement == null) {
                return null;
            }

            // 清理内容
            String content = cleanContent(contentElement.html());

            return new Chapter(chapterIndex, title, content, url);
        } catch (IOException e) {
            System.err.println("爬取章节失败: " + url + ", 错误: " + e.getMessage());
            return null;
        }
    }

    private String getNextPageUrl(String currentUrl, String nextPageSelector) {
        try {
            Document doc = Jsoup.connect(currentUrl)
                    .timeout(timeout)
                    .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
                    .get();

            Element nextLink = doc.select(nextPageSelector).first();
            if (nextLink != null) {
                String nextUrl = nextLink.attr("abs:href");
                if (!nextUrl.isEmpty() && !nextUrl.equals(currentUrl)) {
                    return nextUrl;
                }
            }
        } catch (IOException e) {
            System.err.println("获取下一页失败: " + currentUrl + ", 错误: " + e.getMessage());
        }

        return null;
    }

    private String cleanContent(String html) {
        // 移除脚本和样式
        String cleaned = html.replaceAll("<script[^>]*>[\\s\\S]*?</script>", "")
                .replaceAll("<style[^>]*>[\\s\\S]*?</style>", "");

        // 转换br标签为换行
        cleaned = cleaned.replaceAll("<br\\s*/?>", "\n");

        // 移除所有HTML标签
        cleaned = cleaned.replaceAll("<[^>]+>", "");

        // 处理HTML实体
        cleaned = cleaned.replaceAll("&nbsp;", " ")
                .replaceAll("&lt;", "<")
                .replaceAll("&gt;", ">")
                .replaceAll("&amp;", "&")
                .replaceAll("&quot;", "\"")
                .replaceAll("&apos;", "'");

        // 压缩多余的空行和空格
        cleaned = cleaned.replaceAll("(?m)^[ \t]*\r?\n", "")
                .replaceAll(" +", " ")
                .trim();

        return cleaned;
    }

    @Async
    public String parseDownload(String addr) throws IOException, InterruptedException {
        ClassPathResource resource = new ClassPathResource("/static/http.js");
        List<CrawlConfig> list= JSUtil.convertJsonStringToObject(resource);
        CrawlConfig config=null;
        for (int i = 0; i < list.size(); i++) {
            if(addr.contains(list.get(i).getStartUrl())){
                config=list.get(i);
                break;
            }
        }
        if(null==config){
            System.out.println("网站未配置！");
        }
       String str= this.crawlNovel(config,addr);
        return "Fail".equals(str)?"Fail":"OK";
    }

}